import cp from 'node:child_process';
import fs from 'node:fs/promises';
import path from 'node:path';

/**
 * This script updates the JQuery selector engine for the mock-doc package
 * to the latest version.
 *
 * To run it, use the following command:
 * ```sh
 * npm run tsc.scripts
 * npm run build.updateSelectorEngine
 * ```
 */

const rootDir = path.resolve(__dirname, '..');
const jqueryDepDir = path.resolve(rootDir, 'node_modules', 'jquery');
const WINDOW_MOCK = `{
  document: {
    createElement() {
      return {};
    },
    nodeType: 9,
    documentElement: {
      nodeType: 1,
      nodeName: 'HTML'
    }
  }
}`;

async function run() {
  console.log('updating JQuery Selector engine...');

  await runCommand(`npm install --ignore-scripts`, jqueryDepDir);
  await runCommand(`npm run build -- --include=selector`, jqueryDepDir);

  const jqueryPkgJSON = JSON.parse(await fs.readFile(path.resolve(jqueryDepDir, 'package.json'), 'utf8'));
  const thirdPartyDir = path.resolve(__dirname, '../../src/mock-doc/third-party');
  await fs.mkdir(thirdPartyDir, { recursive: true });

  const originalContent = await fs.readFile(path.resolve(jqueryDepDir, 'dist', 'jquery.js'), 'utf8');

  /**
   * This is a hack to make the jQuery selector engine work with the mock-doc package.
   * Using the original jQuery bundle would cause a "RegExpCompiler Allocation failed - process out of memory"
   * error due to the way the jQuery object is constructed. The following tweaks to the jQuery bundle
   * resolve the issue:
   */
  const fixedJQuery = originalContent
    /**
     * Never run the short-circuiting code due to usage of too many RegExp objects
     */
    .replace('if ( !seed ) {', 'if ( false ) {')
    /**
     * Make jQuery an object to have it garbage collected
     */
    .replace('var version = "', `const jQuery = {} as { find: Function };\nvar version = "`)
    /**
     * Inject window mock directly into iife
     */
    .replace(`typeof window !== "undefined" ? window : this`, WINDOW_MOCK)
    /**
     * Rename the original jQuery function to jQueryOrig
     */
    .replace('jQuery = function( selector, context ) {', 'jQueryOrig = function( selector, context ) {')
    /**
     * Replace use of `jQuery.attr` with `elem.getAttribute` to avoid having to include
     * the attributes plugin to the bundle
     */
    .replace('jQuery.attr( elem, name )', 'elem.getAttribute( name )')
    /**
     * make it return jQuery directly rather than relying on a module system
     */
    .replace('module.exports = factory( global, true );', 'return factory( global, true );')
    .replace('if ( typeof module === "object" && typeof module.exports === "object" ) {', 'if (true) {');

  const newContent = `/* eslint-disable */
// @ts-nocheck

/**
 * ATTENTION: DO NOT MODIFY THIS FILE
 *
 * This file is generated by "scripts/updateSelectorEngine.ts" and can be overwritten
 * at any time. Don't make changes in here as they will get lost!
 */
export default ${fixedJQuery};
`;
  fs.writeFile(path.resolve(thirdPartyDir, 'jquery.ts'), newContent, 'utf8');

  console.log(`\nJQuery Selector engine updated to version ${jqueryPkgJSON.version}`);
  console.log(`at ${thirdPartyDir} 🎉`);
}

function runCommand(cmd: string, cwd: string) {
  return new Promise((resolve, reject) => {
    console.log(`> ${cmd}`);
    const child = cp.spawn(cmd, { cwd, shell: true });
    child.on('error', reject);
    child.on('exit', (code) => (code === 0 ? resolve(child) : reject()));
  });
}

if (require.main === module) {
  run();
}
